<!doctype html>
<html lang="en-US">

<head>
  <title>Part grouping</title>
  <link href="/assets/index.css" rel="stylesheet" type="text/css" />
  <script type="importmap">
      {
        "imports": {
          "react": "https://esm.sh/react@18.3.1",
          "react-dom": "https://esm.sh/react-dom@18.3.1",
          "react-dom/": "https://esm.sh/react-dom@18.3.1/",
          "@testduet/wait-for": "https://unpkg.com/@testduet/wait-for@main/dist/wait-for.mjs",
          "@fluentui/react-components": "https://esm.sh/@fluentui/react-components?deps=react@18.3.1&exports=FluentProvider,createDarkTheme,webLightTheme"
        }
      }
    </script>
  <script crossorigin="anonymous" src="/test-harness.js"></script>
  <script crossorigin="anonymous" src="/test-page-object.js"></script>
  <script type="module">
    import React from 'react';
    window.React = React;
  </script>
  <script defer crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
  <script defer crossorigin="anonymous" src="/__dist__/botframework-webchat-fluent-theme.development.js"></script>
  <style type="text/css">
    #webchat {
      width: 640px;
    }

    .fui-FluentProvider {
      height: 100%;
    }

    .theme.variant-copilot-deprecated {
      --webchat__color--surface: var(--colorGrey98);
    }
  </style>
</head>

<body>
  <main id="webchat"></main>
  <script type="module">
    import React from 'react';
    import { createRoot } from 'react-dom/client';
    import { FluentProvider, createDarkTheme, webLightTheme } from '@fluentui/react-components';
    import { waitFor } from '@testduet/wait-for';

    run(async function () {
      const {
        WebChat: { FluentThemeProvider, ReactWebChat }
      } = window;

      const { directLine, store } = testHelpers.createDirectLineEmulator();

      const searchParams = new URLSearchParams(location.search);
      const variant = searchParams.get('variant');
      const theme = searchParams.get('fluent-theme');

      await host.windowSize(640, 1024, document.getElementById('webchat'));
      await host.sendDevToolsCommand('Emulation.setEmulatedMedia', {
        features: [
          { name: 'prefers-reduced-motion', value: 'reduce' },
          ...(theme === 'dark' || theme === 'light'
            ? [{ name: 'prefers-color-scheme', value: theme }]
            : [])
        ]
      });

      const root = createRoot(document.getElementById('webchat'));

      let fluentTheme;
      let codeBlockTheme;

      if (theme === 'dark' || window.matchMedia('(prefers-color-scheme: dark)').matches && theme !== 'light') {
        fluentTheme = {
          ...createDarkTheme({
            10: '#12174c',
            20: '#1a1f5b',
            30: '#21276a',
            40: '#293079',
            50: '#303788',
            60: '#384097',
            70: '#4049a7',
            80: '#151e80',
            90: '#4f59c5',
            100: '#5661d4',
            110: '#5e69e3',
            120: '#7982e8',
            130: '#949bec',
            140: '#afb5f1',
            150: '#c9cdf6',
            160: '#e4e6fa'
          }),
          colorNeutralBackground1Disabled: '#101010',
          colorNeutralBackground1Hover: '#101010',
          colorNeutralForeground5: '#424242',
          colorGrey98: '#1b1b1b'
        };
        codeBlockTheme = 'github-dark-default';
      } else {
        fluentTheme = {
          ...webLightTheme,
          // Original is #242424 which is too light for fui-FluentProvider to pass our a11y
          colorNeutralForeground1: '#1b1b1b',
          colorGrey98: '#fafafa'
        };
        codeBlockTheme = 'github-light-default';
      }

      // TODO: code block github theme triggers accessibility violation
      if (variant) {
        window.checkAccessibility = async () => { };
      }

      const webChatProps = { directLine, store, styleOptions: { codeBlockTheme } };

      root.render(
        variant ?
          React.createElement(
            FluentProvider,
            { className: 'fui-FluentProvider', theme: fluentTheme },
            React.createElement(
              FluentThemeProvider,
              { variant: variant },
              React.createElement(ReactWebChat, webChatProps)
            )
          ) :
          React.createElement(ReactWebChat, webChatProps)
      );

      await pageConditions.uiConnected();

      const graphSvg = `data:image/svg+xml;utf8,${encodeURIComponent(`<svg width="480" height="480" viewBox="-2 -2 4 4" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Graph of x^2 + y^2 = 0 (single point at origin)">\n  <defs>\n    <marker id="arrow" viewBox="-3 -3 6 6" refX="0" refY="0" markerWidth="4" markerHeight="4" orient="auto">\n      <path d="M-3,-2 L 0,0 L -3,2" fill="none" stroke="#444" stroke-width="0.012"/>\n    </marker>\n    <style><![CDATA[\n      .axis   { stroke:#444; stroke-width:0.012; stroke-linecap:round; }\n      .tickMaj{ stroke:#666; stroke-width:0.008; stroke-linecap:round; }\n      .tickMin{ stroke:#666; stroke-width:0.007200000000000001; stroke-linecap:round; opacity:.75; }\n      .grid   { stroke:#ddd; stroke-width:0.006; }\n      .ring   { fill:#444; stroke: #000; stroke-width:0.01; fill-opacity:0.22; }\n      .label  { fill:#222; font-family:Inter, "Times New Roman", serif; font-size:0.14px; }\n      text    { dominant-baseline:alphabetic; }\n      svg     { shape-rendering:geometricPrecision; }\n    ]]></style>\n  </defs>\n\n  \x3C!-- Flip only geometry so Y points up -->\n  <g transform="scale(1,-1)">\n    \n    \x3C!-- Axes -->\n    <line x1="-2" y1="0" x2="1.94" y2="0" class="axis" marker-end="url(#arrow)"/>\n    <line x1="0" y1="-2" x2="0" y2="1.94" class="axis" marker-end="url(#arrow)"/>\n\n    \x3C!-- Ticks -->\n    <line x1="-2" y1="-0.06" x2="-2" y2="0.06" class="tickMaj"/><line x1="-1" y1="-0.06" x2="-1" y2="0.06" class="tickMaj"/><line x1="1" y1="-0.06" x2="1" y2="0.06" class="tickMaj"/><line x1="2" y1="-0.06" x2="2" y2="0.06" class="tickMaj"/><line x1="-0.06" y1="-2" x2="0.06" y2="-2" class="tickMaj"/><line x1="-0.06" y1="-1" x2="0.06" y2="-1" class="tickMaj"/><line x1="-0.06" y1="1" x2="0.06" y2="1" class="tickMaj"/><line x1="-0.06" y1="2" x2="0.06" y2="2" class="tickMaj"/>\n\n    \x3C!-- The solution: elegant dot + faint ring -->\n    <circle cx="0" cy="0" r="0.05" class="ring"/>\n  </g>\n\n  \x3C!-- Upright labels (SVG coords: +y is down) -->\n  <text x="1.88" y="0.16" class="label">x</text>\n  <text x="-0.12" y="-1.82" class="label">y</text>\n\n  \x3C!-- Numeric labels at ±1 -->\n  <text x="1"   y="0.16" class="label" text-anchor="middle">1</text>\n  <text x="-1"  y="0.16" class="label" text-anchor="middle">−1</text>\n  <text x="-0.14" y="-1" class="label" text-anchor="end">1</text>\n  <text x="-0.14" y="1" class="label" text-anchor="end">−1</text>\n\n  \x3C!-- Equation -->\n  <text x="-1.9" y="1.74" class="label" style="font-style:italic; font-size:0.18px;">x² + y² = 0</text>\n</svg>`)}`;

      const botIcon = '';

      const aiMessageEntity = {
        '@context': 'https://schema.org',
        '@id': '',
        '@type': 'Message',
        keywords: ['AIGeneratedContent', 'AnalysisMessage'],
        type: 'https://schema.org/Message',
        author: {
          '@context': 'https://schema.org',
          '@type': 'Person',
          image: botIcon,
          name: 'Research',
        }
      };

      const withCreativeWorkStatus = (activity, status) => ({
        ...activity,
        entities: activity.entities.map(entity => ({
          ...entity,
          creativeWorkStatus: status
        }))
      });

      directLine.emulateIncomingActivity({
        from: { role: 'user' },
        type: 'message',
        text: `Message from user.`
      });

      const activities = [
        {
          entities: [
            {
              ...aiMessageEntity,
              '@id': '',
              '@type': 'Message',
              type: 'https://schema.org/Message',
              abstract: 'Considering equation solutions',
              position: 1,
              isPartOf: {
                '@id': '_:h-00001',
                '@type': 'HowTo'
              }
            }
          ],
          id: 'a-00001',
          text: 'The equation \\(x^2 + y^2 = 0\\) has only one solution: \\((0, 0)\\). This means the graph would only plot a single point at the origin.'
        },
        {
          entities: [
            {
              ...aiMessageEntity,
              '@id': '',
              '@type': 'Message',
              type: 'https://schema.org/Message',
              abstract: 'Planning plot implementation',
              position: 2,
              isPartOf: {
                '@id': '_:h-00001',
                '@type': 'HowTo'
              }
            }
          ],
          id: 'a-00002',
          text: "I'm preparing to plot the equation \\(x^2 + y^2 = 0\\) as a single point at the origin. I'll use matplotlib to create a scatter plot and save the graph as a PNG file."
        },
        {
          entities: [
            {
              '@id': '',
              '@type': 'Message',
              type: 'https://schema.org/Message',
              abstract: 'Writing plot code',
              position: 3,
              isPartOf: {
                '@id': '_:h-00001',
                '@type': 'HowTo'
              }
            }
          ],
          id: 'a-00003',
          text: "I'm preparing to generate Python code using matplotlib to scatter a point at the origin \\((0,0)\\), representing the equation \\(x^2 + y^2 = 0\\). I'll include gridlines for better visualization and save the plot as a PNG."
        },
        {
          channelData: {
            streamId: 'a-00003',
            streamSequence: 1,
            streamType: 'informative',
          },
          entities: [
            {
              '@id': '',
              '@type': 'Message',
              type: 'https://schema.org/Message',
              abstract: 'Coding & Executing ...',
              position: 4,
              isBasedOf: [
                {
                  '@type': 'SoftwareSourceCode',
                  text: '# Plot a graph for '
                }
              ],
              isPartOf: {
                '@id': '_:h-00001',
                '@type': 'HowTo'
              },
            }
          ],
          id: 'a-00004',
          text: "I'm wrtiting Python code to plot the equation \\(x^2 + y^2 = 0\\) using matplotlib. The code will create a scatter plot with a single point at the origin, add gridlines and axis labels, and save the plot as 'output.png'."
        },
        {
          channelData: {
            streamId: 'a-00004',
            // final is causing troubles with updating message entity
            // streamType: 'final',
          },
          entities: [
            {
              '@id': '',
              '@type': 'Message',
              type: 'https://schema.org/Message',
              abstract: 'Coding & Executing ...',
              position: 4,
              isBasedOn: [
                {
                  '@type': 'SoftwareSourceCode',
                  programmingLanguage: 'python',
                  text: "# Plot a graph for equation x^2 + y^2 = 0 using Python, and save the result as output\nimport matplotlib.pyplot as plt\nimport numpy as np\n\n# Only point that satisfies x^2 + y^2 = 0 is (0, 0)\nx = [0]\ny = [0]\n\n# Create a figure and axis\nfig, ax = plt.subplots()\nax.scatter(x, y, color='red', s=100)  # plot the single point in red with size 100.\n\n# Add grid and axis lines that cross the center\nax.axhline(color='black', linewidth=0.5)\nax.axvline(color='black', linewidth=0.5)\n\n# Setting the aspect ratio to be equal\nax.set_aspect('equal')\n\n# Label axes\nax.set_xlabel('x')\nax.set_ylabel('y')\n\ntitle = \"Graph of equation x^2 + y^2 = 0\"\nax.set_title(title)\n\n# Set the limits for x and y axes to see point clearly\nax.set_xlim(-1,1)\nax.set_ylim(-1,1)\n\n# Save the plot to a file\nplt.savefig('output.png')\nplt.close(fig)"
                }
              ],
              isPartOf: {
                '@id': '_:h-00001',
                '@type': 'HowTo'
              },
              keywords: ['Collapsible']
            }
          ],
          id: 'a-00004',
          text: "I'm executing the Python code to plot the equation \\(x^2 + y^2 = 0\\). The code creates a scatter plot with a single red point at the origin, adds gridlines and axis labels, and saves the plot as 'output.png'."
        },
        {
          text: `The equation \\(x^2 + y^2 = 0\\) has only one real solution: the point at the origin \\((0, 0)\\). This is because both \\(x^2\\) and \\(y^2\\) are non-negative, and their sum can only be zero if both are zero.\n\nHere's the graph showing that single point:\n\n![Graph](${graphSvg})\n\nWould you like to explore similar equations or see how this compares to something like \\(x^2 + y^2 = r^2\\) for \\(r > 0\\)?`
        }
      ];

      if (variant !== 'copilot') {
        throw new Error(`The variant "${variant}" is not supported in this test.`);
      }

      // When activities are sent:
      // 1st is sent with status 'Incomplete' to show the work has started
      directLine.emulateIncomingActivity(withCreativeWorkStatus(activities.at(0), 'Incomplete'));
      // 2nd is sent with status not set to show the work is in
      directLine.emulateIncomingActivity(withCreativeWorkStatus(activities.at(1), undefined));

      // Then: show both activities in the list:
      // 1st with the loading indicator
      // 2nd with an empty status indicator
      await pageConditions.numActivitiesShown(3);
      await host.snapshot('local');

      // When folding the list:
      await host.click(document.querySelector('.collapsible-grouping__toggle'));

      // Then: show loaded activity (copilot)
      await pageConditions.numActivitiesShown(3);
      await host.snapshot('local');

      // When marking the 1st activity as 'Published' and 2nd as 'Incomplete':
      directLine.emulateIncomingActivity(withCreativeWorkStatus(activities.at(0), 'Published'));
      directLine.emulateIncomingActivity(withCreativeWorkStatus(activities.at(1), 'Incomplete'));

      // Then: show the 2nd activity in the folded list with the loading status indicator
      await pageConditions.numActivitiesShown(3);
      await host.snapshot('local');

      // When adding activities to the list:
      directLine.emulateIncomingActivity(withCreativeWorkStatus(activities.at(2), undefined));
      directLine.emulateIncomingActivity(withCreativeWorkStatus(activities.at(3), undefined));

      // Then: show only the 2nd activity with 'Incomplete' status in the folded list
      await pageConditions.numActivitiesShown(5);
      await host.snapshot('local');

      // When marking the 3nd activity as 'Incomplete':
      directLine.emulateIncomingActivity(withCreativeWorkStatus(activities.at(2), 'Incomplete'));

      // Then: show the 2nd and 3rd activity in the folded list with the loading status indicator
      await pageConditions.numActivitiesShown(5);
      await host.snapshot('local');

      // When marking the 2nd activity as 'Published':
      directLine.emulateIncomingActivity(withCreativeWorkStatus(activities.at(1), 'Published'));

      // Then: show only the 3rd activity in the folded list with the loading status indicator
      await pageConditions.numActivitiesShown(5);
      await host.snapshot('local');

      // When setting status of the last activity to 'Incomplete':
      directLine.emulateIncomingActivity(withCreativeWorkStatus(activities.at(3), 'Incomplete'));

      // Then: the 3rd and the 4th activities should be shown in the folded list with loading status indicators
      await pageConditions.numActivitiesShown(5);
      await host.snapshot('local');

      // When updating the last activity with code content:
      directLine.emulateIncomingActivity(withCreativeWorkStatus(activities.at(4), 'Incomplete'));

      // Then: the 3rd and the 4th activities should be shown in the folded list with loading status indicators
      // Then: the 4th activity should have updated content
      await pageConditions.numActivitiesShown(5);
      await host.snapshot('local');

      // When marking all remaining activities with 'Incomplete' status as 'Published':
      directLine.emulateIncomingActivity(withCreativeWorkStatus(activities.at(2), 'Published'));
      directLine.emulateIncomingActivity(withCreativeWorkStatus(activities.at(4), 'Published'));

      // Then: the whole group of activities should show the completion icon in the collapsed state
      await pageConditions.numActivitiesShown(5);
      await host.snapshot('local');

      // When adding a closing message:
      directLine.emulateIncomingActivity(activities.at(5));

      // Then: show the closing message after the folded group
      await pageConditions.numActivitiesShown(6);
      await host.snapshot('local');
    });
  </script>
</body>

</html>
